{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#  项目分析：\n",
    "- 构成：\n",
    "    - 蛇 Snake\n",
    "    - 食物 Food\n",
    "    - 世界 World\n",
    "    \n",
    "- 蛇和食物属于整个世界\n",
    "    \n",
    "        class World:\n",
    "            self.snake\n",
    "            self.food\n",
    "    - 上面代码不太友好\n",
    "    - 我们用另外一个思路来分析\n",
    "    \n",
    "- 我们的分析思路\n",
    "    - 食物是一个独立的事物\n",
    "    - 蛇也可以认为是一个独立的事物\n",
    "    - 世界也是，但世界负责显示"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import queue\n",
    "import time\n",
    "from tkinter import *\n",
    "import threading\n",
    "import random\n",
    "\n",
    "\n",
    "class Food():\n",
    "    '''\n",
    "    功能：\n",
    "        1. 出现在画面的某一个地方\n",
    "        2. 一旦被吃，则增加蛇的分数\n",
    "\n",
    "    '''\n",
    "    def __init__(self, queue):\n",
    "        '''\n",
    "        自动产生一个食物\n",
    "        '''\n",
    "        self.queue = queue\n",
    "        self.new_food()\n",
    "        \n",
    "    def new_food(self):\n",
    "        '''\n",
    "        功能：产生一个食物\n",
    "        产生一个食物的过程就是随机产生一个食物坐标的过程\n",
    "        '''\n",
    "        # 注意横纵坐标产生的范围\n",
    "        x = random.randrange(5, 490, 10)\n",
    "        # 同理产生y坐标\n",
    "        y = random.randrange(5, 290, 10)\n",
    "        # 需要注意的是，我们的正给游戏屏幕一般不需要把他设置成正方形\n",
    "        \n",
    "        \n",
    "        self.postion = x,y # position存放食物的位置\n",
    "        self.exppos = x - 5, y - 5, x + 5, y + 5\n",
    "        # 队列，就是一个不能够随意访问内部元素，只能从头弹出一个元素并只能\n",
    "        #从队尾追加元素的list\n",
    "        #  把一个食物产生的消息放入队列\n",
    "        # 消息的格式，自己定义\n",
    "        # 我的定义是： 消息是一个dict， k代表消息类型，v代表此类型的数据\n",
    "        self.queue.put({\"food\": self.exppos})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Snake(threading.Thread):\n",
    "    '''\n",
    "    蛇的功能：\n",
    "        1. 蛇能动，由我们的上下左右按键控制\n",
    "        2. 蛇每次动，都需要重新计算蛇头的位置\n",
    "        3. 检测是否游戏完事的功能\n",
    "    '''\n",
    "    def __init__(self, world, queue):\n",
    "        \n",
    "        threading.Thread.__init__(self)\n",
    "        \n",
    "        self.world = world\n",
    "        self.queue = queue\n",
    "        self.daemon = True\n",
    "        self.points_earned = 0 #游戏分数\n",
    "        self.food = Food(queue)\n",
    "        self.snake_points = [(495, 55), (485,55), (465,55), (455,55)]\n",
    "        self.direction = 'Left'\n",
    "        self.start()\n",
    "        \n",
    "        \n",
    "    def run(self):\n",
    "        '''\n",
    "        一旦启用多线程调用此函数\n",
    "        要求蛇一直都在跑\n",
    "        '''\n",
    "        if self.world.is_game_over:\n",
    "            self._delete()\n",
    "        \n",
    "        while not self.world.is_game_over:\n",
    "            self.queue.put({\"move\": self.snake_points})\n",
    "            time.sleep(0.5) #控制蛇的速度\n",
    "            self.move()\n",
    "        \n",
    "        \n",
    "    \n",
    "    def move(self):\n",
    "        '''\n",
    "        负责蛇的移动\n",
    "        1. 重新计算蛇头的坐标\n",
    "        2. 当蛇头跟食物相遇，则加分，重新生成食物，通知world，加分\n",
    "        3. 否则, 蛇需要动\n",
    "        '''\n",
    "        new_snake_point = self.cal_new_position() #重新计算蛇头位置\n",
    "        \n",
    "        # 蛇头位置跟食物位置相同\n",
    "        if self.food.postion == new_snake_point:\n",
    "            self.points_earned += 1 #得分加1\n",
    "            self.queue.put({\"points_earned\": self.points_earned})\n",
    "            self.food.new_food() #就得食物被吃掉，产生新的食物\n",
    "        else:\n",
    "            # 需要注意蛇的信息的保存方式\n",
    "            # 每次移动是删除存放蛇的最前位置，并在后面追加\n",
    "            self.snake_points.pop(0)\n",
    "            # 判断程序是否退出，因为新的蛇可能撞墙\n",
    "            self.check_game_over(new_snake_point)\n",
    "            self.snake_points.append(new_snake_point)\n",
    "    def cal_new_position(self): \n",
    "        '''\n",
    "        计算新的 蛇头的位置\n",
    "        '''\n",
    "        last_x, last_y = self.snake_points[-1]\n",
    "        if self.direction == \"Up\": #direction负责存储蛇移动的方向\n",
    "            new_snake_point = last_x, last_y - 10 #每次移动的跨度是10像素\n",
    "        elif self.direction == 'Down':\n",
    "            # 需要总共判断上下左右四个方向\n",
    "            new_snake_point = last_x, last_y + 10\n",
    "        elif self.direction == 'Left':\n",
    "            new_snake_point = last_x - 10, last_y\n",
    "        elif self.direction == 'Right':\n",
    "            new_snake_point = last_x + 10, last_y\n",
    "            \n",
    "            \n",
    "        return new_snake_point\n",
    "        \n",
    "    def key_pressed(self, e):\n",
    "        # keysym是按键名称\n",
    "        self.direction = e.keysym\n",
    "        \n",
    "    def check_game_over(self, snake_point):\n",
    "        '''\n",
    "        判断的依据是蛇头是否和墙相撞\n",
    "        '''\n",
    "        # 把蛇头的坐标拿出来，跟墙的坐标进行判断\n",
    "        x,y = snake_point[0], snake_point[1]\n",
    "        if not -5 < x < 505 or not -5 < y < 315 or snake_point in self.snake_points:\n",
    "            self.queue.put({'game_over': True})\n",
    " "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class World(Tk):\n",
    "    '''\n",
    "    用来模拟整个游戏画板\n",
    "    '''\n",
    "    def __init__(self, queue):\n",
    "        Tk.__init__(self)\n",
    "        \n",
    "        self.queue = queue\n",
    "        self.is_game_over = False\n",
    "        \n",
    "        # 定义画板\n",
    "        self.canvas = Canvas(self, width=500, height=300, bg='gray')\n",
    "        self.canvas.pack()\n",
    "        \n",
    "        \n",
    "        # 画出蛇和食物\n",
    "        self.snake = self.canvas.create_line((0,0), (0,0), fill=\"black\", width=10)\n",
    "        self.food = self.canvas.create_rectangle(0,0,0,0,fill='black', outline='#FFCC4C')\n",
    "\n",
    "        self.points_earned = self.canvas.create_text(450, 20,fill='white', text='SCORE: 0')\n",
    "        self.queue_handler()\n",
    "  \n",
    "\n",
    "    def queue_handler(self):\n",
    "        try:\n",
    "            # 需要不断从消息队列拿到消息，所以使用死循环\n",
    "            while True:\n",
    "                task = self.queue.get(block=False)\n",
    "            \n",
    "                if task.get(\"game_over\"):\n",
    "                    self.game_over()\n",
    "                if task.get(\"move\"):\n",
    "                    points =  [ x for point in task['move'] for x in point]\n",
    "                    #重新绘制蛇\n",
    "                    self.canvas.coords(self.snake, *points)\n",
    "\n",
    "                # 同样道理，还需要处理食物，得分\n",
    "                if task.get('food'):\n",
    "                    self.canvas.coords(self.food, *task['food'])\n",
    "                elif task.get('points_earned'):\n",
    "                    self.canvas.itemconfigure(self.points_earned,\n",
    "                                              text='SCORE: {}'.format(task['points_earned']))\n",
    "                self.queue.task_done()\n",
    "                \n",
    "        except queue.Empty: #爆出队列为空异常\n",
    "            if not self.is_game_over:\n",
    "                # after的含义是，在多少毫秒后调用后面的函数\n",
    "                self.canvas.after(100, self.queue_handler)\n",
    "    \n",
    "    def game_over(self):\n",
    "        '''\n",
    "        游戏结束，清理现场\n",
    "        '''\n",
    "        self.is_game_over = True\n",
    "        self.canvas.create_text(200,150, fill='white', text='Game Over')\n",
    "        qb = Button(self, text=\"Quit\", command=self.destroy)\n",
    "        rb = Button(self, text=\"Again\", command=self.__init__)\n",
    "        self.canvas.create_window(200, 180, anchor='nw', window=qb)        \n",
    "                \n",
    "def main():\n",
    "    q = queue.Queue()\n",
    "    world = World(q)\n",
    "    world.title(\"傻傻的贪吃蛇\")\n",
    "    snake = Snake(world, q)\n",
    "    \n",
    "    world.bind('<Key-Left>', snake.key_pressed)\n",
    "    # 同样绑定右键，上下键\n",
    "    world.bind('<Key-Right>', snake.key_pressed)\n",
    "    world.bind('<Key-Up>', snake.key_pressed)\n",
    "    world.bind('<Key-Down>', snake.key_pressed)\n",
    "    world.mainloop()\n",
    "\n",
    "    \n",
    "if __name__ == \"__main__\": \n",
    "    main()                       "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
